#![allow(unused_mut)]
#![allow(unused_imports)]

use serde::Deserialize;
use specta::Type;
use std::{path::PathBuf, str::FromStr};
use tauri::{
    AppHandle, LogicalPosition, Manager, Monitor, PhysicalPosition, PhysicalSize, WebviewUrl,
    WebviewWindow, WebviewWindowBuilder, Wry,
};

#[cfg(target_os = "macos")]
const DEFAULT_TRAFFIC_LIGHTS_INSET: LogicalPosition<f64> = LogicalPosition::new(12.0, 12.0);

#[derive(Clone)]
pub enum ChatWindowId {
    Main,
    Settings,
    Launcher,
}

impl FromStr for ChatWindowId {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "main" => Self::Main,
            "settings" => Self::Settings,
            "launcher" => Self::Launcher,
            _ => return Err(format!("unknown window label: {}", s)),
        })
    }
}

impl std::fmt::Display for ChatWindowId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Settings => write!(f, "settings"),
            Self::Launcher => write!(f, "launcher"),
            _ => write!(f, "main"),
        }
    }
}

impl ChatWindowId {
    pub fn label(&self) -> String {
        self.to_string()
    }

    pub fn title(&self) -> String {
        match self {
            Self::Settings => "Chat Settings".to_string(),
            Self::Launcher => "AI Launcher".to_string(),
            _ => "Chat".to_string(),
        }
    }

    pub fn activates_dock(&self) -> bool {
        matches!(self, Self::Main | Self::Settings)
    }

    pub fn get(&self, app: &AppHandle<Wry>) -> Option<WebviewWindow> {
        let label = self.label();
        app.get_webview_window(&label)
    }

    #[cfg(target_os = "macos")]
    pub fn traffic_lights_position(&self) -> Option<Option<LogicalPosition<f64>>> {
        match self {
            Self::Settings { .. } => Some(Some(LogicalPosition::new(20.0, 40.0))),
            _ => Some(None),
        }
    }

    pub fn min_size(&self) -> Option<(f64, f64)> {
        Some(match self {
            Self::Main => (300.0, 360.0),
            Self::Settings => (600.0, 450.0),
            Self::Launcher => (600.0, 80.0),
            _ => return None,
        })
    }
}

#[derive(Clone)]
pub enum ShowChatWindow {
    Main,
    Settings { page: Option<String> },
    Launcher,
}

impl ShowChatWindow {
    pub fn show(&self, app: &AppHandle<Wry>) -> tauri::Result<WebviewWindow> {
        if let Some(window) = self.id().get(app) {
            window.set_focus().ok();
            return Ok(window);
        }

        let id = self.id();

        let window = match self {
            Self::Main => self
                .window_builder(app, "/")
                .resizable(false)
                .maximized(false)
                .maximizable(false)
                .center()
                .build()?,
            Self::Settings { page } => self
                .window_builder(
                    app,
                    format!("/settings/{}", page.clone().unwrap_or_default()),
                )
                .resizable(true)
                .maximized(false)
                .center()
                .build()?,
            Self::Launcher => self
                .window_builder(app, "/launcher")
                .resizable(false)
                .maximized(false)
                .maximizable(false)
                .center()
                .build()?,
        };

        window.hide().ok();

        #[cfg(target_os = "macos")]
        if let Some(position) = id.traffic_lights_position() {
            add_traffic_lights(&window, position);
        }

        Ok(window)
    }

    fn window_builder<'a>(
        &'a self,
        app: &'a AppHandle<Wry>,
        url: impl Into<PathBuf>,
    ) -> WebviewWindowBuilder<'a, Wry, AppHandle<Wry>> {
        let id = self.id();

        let mut builder = WebviewWindow::builder(app, id.label(), WebviewUrl::App(url.into()))
            .title(id.title())
            .visible(false)
            .accept_first_mouse(true)
            .shadow(true);

        if let Some(min) = id.min_size() {
            builder = builder
                .inner_size(min.0, min.1)
                .min_inner_size(min.0, min.1);
        }

        #[cfg(target_os = "macos")]
        {
            if id.traffic_lights_position().is_some() {
                builder = builder
                    .hidden_title(true)
                    .title_bar_style(tauri::TitleBarStyle::Overlay);
            } else {
                builder = builder.decorations(false)
            }
        }

        #[cfg(target_os = "windows")]
        {
            builder = builder.decorations(false);
        }

        builder
    }

    pub fn id(&self) -> ChatWindowId {
        match self {
            ShowChatWindow::Main => ChatWindowId::Main,
            ShowChatWindow::Settings { .. } => ChatWindowId::Settings,
            ShowChatWindow::Launcher => ChatWindowId::Launcher,
        }
    }
}

#[cfg(target_os = "macos")]
fn add_traffic_lights(window: &WebviewWindow<Wry>, controls_inset: Option<LogicalPosition<f64>>) {
    use crate::platform::delegates;

    let target_window = window.clone();
    window
        .run_on_main_thread(move || {
            delegates::setup(
                target_window.as_ref().window(),
                controls_inset.unwrap_or(DEFAULT_TRAFFIC_LIGHTS_INSET),
            );

            let c_win = target_window.clone();
            target_window.on_window_event(move |event| match event {
                tauri::WindowEvent::ThemeChanged(..) | tauri::WindowEvent::Focused(..) => {
                    position_traffic_lights_impl(
                        &c_win.as_ref().window(),
                        controls_inset.map(LogicalPosition::from),
                    );
                }
                _ => {}
            });
        })
        .ok();
}

#[tauri::command]
#[specta::specta]
pub fn set_theme(window: tauri::Window, theme: AppTheme) {
    let _ = window.set_theme(match theme {
        AppTheme::System => None,
        AppTheme::Light => Some(tauri::Theme::Light),
        AppTheme::Dark => Some(tauri::Theme::Dark),
    });

    #[cfg(target_os = "macos")]
    match ChatWindowId::from_str(window.label()) {
        Ok(win) if win.traffic_lights_position().is_some() => position_traffic_lights(window, None),
        Ok(_) | Err(_) => {}
    }
}

#[tauri::command]
#[specta::specta]
pub fn position_traffic_lights(window: tauri::Window, controls_inset: Option<(f64, f64)>) {
    #[cfg(target_os = "macos")]
    position_traffic_lights_impl(
        &window,
        controls_inset.map(LogicalPosition::from).or_else(|| {
            // Attempt to get the default inset from the window's traffic lights position
            ChatWindowId::from_str(window.label())
                .ok()
                .and_then(|id| id.traffic_lights_position().flatten())
        }),
    );
}

#[cfg(target_os = "macos")]
fn position_traffic_lights_impl(
    window: &tauri::Window,
    controls_inset: Option<LogicalPosition<f64>>,
) {
    use crate::platform::delegates::{position_window_controls, UnsafeWindowHandle};
    let c_win = window.clone();
    window
        .run_on_main_thread(move || {
            let ns_window = match c_win.ns_window() {
                Ok(handle) => handle,
                Err(_) => return,
            };
            position_window_controls(
                UnsafeWindowHandle(ns_window),
                &controls_inset.unwrap_or(DEFAULT_TRAFFIC_LIGHTS_INSET),
            );
        })
        .ok();
}

// Credits: tauri-plugin-window-state
trait MonitorExt {
    fn intersects(&self, position: PhysicalPosition<i32>, size: PhysicalSize<u32>) -> bool;
}

impl MonitorExt for Monitor {
    fn intersects(&self, position: PhysicalPosition<i32>, size: PhysicalSize<u32>) -> bool {
        let PhysicalPosition { x, y } = *self.position();
        let PhysicalSize { width, height } = *self.size();

        let left = x;
        let right = x + width as i32;
        let top = y;
        let bottom = y + height as i32;

        [
            (position.x, position.y),
            (position.x + size.width as i32, position.y),
            (position.x, position.y + size.height as i32),
            (
                position.x + size.width as i32,
                position.y + size.height as i32,
            ),
        ]
        .into_iter()
        .any(|(x, y)| x >= left && x < right && y >= top && y < bottom)
    }
}
